import React, { useState, useRef, useEffect } from 'react'
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  runOnUI,
  useAnimatedGestureHandler,
  interpolate,
  Extrapolate,
  withTiming,
  Easing,
} from 'react-native-reanimated'
import {
  Dimensions,
  StyleSheet,
  View,
  Image,
  Platform,
  Pressable,
} from 'react-native'
import { ScrollView, PanGestureHandler } from 'react-native-gesture-handler'

const Header = {
  HEIGHT: 0,
}

const AnimatedImage = Animated.createAnimatedComponent(Image)

const dimensions = Dimensions.get('window')
const GUTTER_WIDTH = 3
const NUMBER_OF_IMAGES = 4
const IMAGE_SIZE =
  (dimensions.width - GUTTER_WIDTH * (NUMBER_OF_IMAGES - 1)) / NUMBER_OF_IMAGES

const styles = StyleSheet.create({
  container: {
    paddingTop: 0,
    height:
      Platform.OS === 'web' ? dimensions.height - Header.HEIGHT : undefined,
  },

  scrollContainer: {
    flexDirection: 'row',
    flexWrap: 'wrap',
  },

  backdrop: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: 'black',
  },
})

function ImageList({ images, onItemPress }) {
  return (
    <ScrollView contentContainerStyle={styles.scrollContainer}>
      {images.map((item, i) => (
        <ListItem onPress={onItemPress} key={i} index={i} item={item} />
      ))}
    </ScrollView>
  )
}

function ListItem({ item, index, onPress }) {
  const ref = useRef()
  const opacity = useSharedValue(1)

  const containerStyle = {
    marginRight: (index + 1) % 4 === 0 ? 0 : GUTTER_WIDTH,
    marginBottom: GUTTER_WIDTH,
  }

  const styles = useAnimatedStyle(() => {
    return {
      width: IMAGE_SIZE,
      height: IMAGE_SIZE,
      opacity: opacity.value,
    }
  })

  return (
    <Pressable
      style={containerStyle}
      onPress={() => onPress(ref, item, opacity)}
    >
      <AnimatedImage ref={ref} source={{ uri: item.uri }} style={styles} />
    </Pressable>
  )
}

const timingConfig = {
  duration: 350,
  easing: Easing.bezier(0.33, 0.01, 0, 1),
}

function ImageTransition({ activeImage, onClose }) {
  const {
    x,
    item,
    width,
    height,
    targetWidth,
    targetHeight,
    sv: imageOpacity,
  } = activeImage
  const { uri } = item
  const y = activeImage.y - Header.HEIGHT

  const animationProgress = useSharedValue(0)

  const backdropOpacity = useSharedValue(0)
  const scale = useSharedValue(1)

  const targetX = useSharedValue(0)
  const targetY = useSharedValue(
    (dimensions.height - targetHeight) / 2 - Header.HEIGHT
  )

  const translateX = useSharedValue(0)
  const translateY = useSharedValue(0)

  const onEnd = (forceClose?: boolean) => {
    if (Math.abs(translateY.value) > 40 || forceClose) {
      targetX.value = translateX.value - targetX.value * -1
      targetY.value = translateY.value - targetY.value * -1

      translateX.value = 0
      translateY.value = 0

      animationProgress.value = withTiming(0, timingConfig, () => {
        imageOpacity.value = 1
        onClose()
      })

      backdropOpacity.value = withTiming(0, timingConfig)
    } else {
      backdropOpacity.value = withTiming(1, timingConfig)
      translateX.value = withTiming(0, timingConfig)
      translateY.value = withTiming(0, timingConfig)
    }

    scale.value = withTiming(1, timingConfig)
  }

  const onPan = useAnimatedGestureHandler({
    onActive: (event) => {
      translateX.value = event.translationX
      translateY.value = event.translationY

      scale.value = interpolate(
        translateY.value,
        [-250, 0, 250],
        [0.65, 1, 0.65]
        // Extrapolate.CLAMP
      )

      backdropOpacity.value = interpolate(
        translateY.value,
        [-100, 0, 100],
        [0, 1, 0],
        Extrapolate.CLAMP
      )
    },
    onEnd: () => onEnd(),
  })

  const imageStyles = useAnimatedStyle(() => {
    const interpolateProgress = (range) =>
      interpolate(animationProgress.value, [0, 1], range, Extrapolate.CLAMP)

    const top = translateY.value + interpolateProgress([y, targetY.value])
    const left = translateX.value + interpolateProgress([x, targetX.value])

    return {
      position: 'absolute',
      top,
      left,
      width: interpolateProgress([width, targetWidth]),
      height: interpolateProgress([height, targetHeight]),
      transform: [
        {
          scale: scale.value,
        },
      ],
    }
  })

  const backdropStyles = useAnimatedStyle(() => {
    return {
      opacity: backdropOpacity.value,
    }
  })

  useEffect(() => {
    runOnUI(() => {
      'worklet'
      animationProgress.value = withTiming(1, timingConfig, () => {
        imageOpacity.value = 0
      })
      backdropOpacity.value = withTiming(1, timingConfig)
    })()
  }, [])

  return (
    <View style={StyleSheet.absoluteFillObject}>
      <Animated.View style={[styles.backdrop, backdropStyles]} />

      <Pressable
        style={StyleSheet.absoluteFillObject}
        onPress={() => onEnd(true)}
      >
        <PanGestureHandler onGestureEvent={onPan}>
          <Animated.View style={StyleSheet.absoluteFillObject}>
            <AnimatedImage source={{ uri }} style={imageStyles} />
          </Animated.View>
        </PanGestureHandler>
      </Pressable>
    </View>
  )
}

const images = Array.from({ length: 30 }, (_, index) => {
  return {
    uri: `https://picsum.photos/id/${index + 10}/400/400`,
    width: dimensions.width,
    height: 400,
  }
})

export default function LightboxExample() {
  const [activeImage, setActiveImage] = useState(null)

  function onItemPress(imageRef, item, sv) {
    imageRef.current.measure((x, y, width, height, pageX, pageY) => {
      if (width === 0 && height === 0) {
        return
      }

      const targetWidth = dimensions.width
      const scaleFactor = item.width / targetWidth
      const targetHeight = item.height / scaleFactor

      setActiveImage({
        item,
        width,
        height,
        x: pageX,
        y: pageY,
        targetHeight,
        targetWidth,
        sv,
      })
    })
  }

  function onClose() {
    setActiveImage(null)
  }

  return (
    <View style={styles.container}>
      <ImageList onItemPress={onItemPress} images={images} />

      {activeImage && (
        <ImageTransition onClose={onClose} activeImage={activeImage} />
      )}
    </View>
  )
}
